#include "Clob.hxx"
#include "Connection.hxx"
#include "DatabaseMetaData.hxx"
+#include "Driver.hxx"
#include "PreparedStatement.hxx"
#include "Statement.hxx"
#include "Util.hxx"
}
void Connection::construct(const OUString& url, const Sequence< PropertyValue >& info,
- const OUString& rDatabaseDataDirectoryURL)
+ FirebirdDriver& rDriver)
{
ConnectionGuard aGuard(m_refCount);
try
{
m_sConnectionURL = url;
+ m_xDriver = &rDriver;
bool bIsNewDatabase = false;
// the database may be stored as an
bIsNewDatabase = !m_xEmbeddedStorage->hasElements();
- m_pDatabaseFileDir.reset(new ::utl::TempFileNamed(&rDatabaseDataDirectoryURL, true));
+ const OUString sDatabaseDataDirectoryURL = rDriver.getDatabaseDataDirectoryURL();
+ m_pDatabaseFileDir.reset(new ::utl::TempFileNamed(&sDatabaseDataDirectoryURL, true));
m_pDatabaseFileDir->EnableKillingFile();
m_sFirebirdURL = m_pDatabaseFileDir->GetFileName() + "/firebird.fdb";
m_sFBKPath = m_pDatabaseFileDir->GetFileName() + "/firebird.fbk";
bIsNewDatabase = true;
osl::FileBase::getSystemPathFromFileURL(m_sFirebirdURL, m_sFirebirdURL);
+
+ // The user picked this file, so it is outside the directory firebird is
+ // restricted to. Reach it by the name the driver gives us for it.
+ m_sExternalDatabaseName = rDriver.addExternalDatabaseName(m_sFirebirdURL);
}
}
ISC_STATUS_ARRAY status; /* status vector */
ISC_STATUS aErr;
- const OString sFirebirdURL = OUStringToOString(m_sFirebirdURL, RTL_TEXTENCODING_UTF8);
+ const OString sDatabaseName
+ = OUStringToOString(getFirebirdDatabaseName(), RTL_TEXTENCODING_UTF8);
if (bIsNewDatabase)
{
aErr = isc_create_database(status,
- sFirebirdURL.getLength(),
- sFirebirdURL.getStr(),
+ sDatabaseName.getLength(),
+ sDatabaseName.getStr(),
&m_aDBHandle,
dpbBuffer.size(),
dpbBuffer.c_str(),
}
aErr = isc_attach_database(status,
- sFirebirdURL.getLength(),
- sFirebirdURL.getStr(),
+ sDatabaseName.getLength(),
+ sDatabaseName.getStr(),
&m_aDBHandle,
dpbBuffer.size(),
dpbBuffer.c_str());
storeDatabase();
+ if (!m_sExternalDatabaseName.isEmpty())
+ {
+ m_xDriver->removeExternalDatabaseName(m_sExternalDatabaseName);
+ m_sExternalDatabaseName.clear();
+ }
+
cppu::WeakComponentImplHelperBase::disposing();
m_pDatabaseFileDir.reset();
#include <cppuhelper/compbase.hxx>
#include <cppuhelper/weakref.hxx>
#include <memory>
+#include <rtl/ref.hxx>
#include <OTypeInfo.hxx>
#include <unotools/tempfile.hxx>
/** We are using an external (local) file */
bool m_bIsFile;
+ /** The driver that made this connection, and owns the directory firebird works in. */
+ rtl::Reference<FirebirdDriver> m_xDriver;
+
+ /**
+ * The name firebird was given for a database outside its own directory, empty when the
+ * database is inside it and its path was handed over directly.
+ */
+ OUString m_sExternalDatabaseName;
+
+ /** The name or path this connection hands to firebird to reach its database. */
+ const OUString& getFirebirdDatabaseName() const
+ {
+ return m_sExternalDatabaseName.isEmpty() ? m_sFirebirdURL : m_sExternalDatabaseName;
+ }
+
/** When true the embedded database was opened in this session, so the
temporary .fdb holds live data that must be written back into the .odb
when the connection is disposed. */
/// @throws css::uno::RuntimeException
void construct( const OUString& url,
const css::uno::Sequence< css::beans::PropertyValue >& info,
- const OUString& rDatabaseDataDirectoryURL);
+ FirebirdDriver& rDriver);
const OUString& getConnectionURL() const {return m_sConnectionURL;}
bool isEmbedded() const {return m_bIsEmbedded;}
#include <strings.hrc>
#include <resource/sharedresources.hxx>
+#include <config_firebird.h>
+
+// firebird's own header has functions with parameters it does not use
+#if defined __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#endif
+#if defined _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4100) // unreferenced formal parameter
+#endif
+#include <firebird/Interface.h>
+#if defined _MSC_VER
+#pragma warning(pop)
+#endif
+#if defined __GNUC__
+#pragma GCC diagnostic pop
+#endif
+
#include <comphelper/servicehelper.hxx>
#include <cppuhelper/supportsservice.hxx>
#include <osl/file.hxx>
#include <osl/process.h>
+#include <osl/thread.h>
#include <rtl/bootstrap.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/uuid.h>
#include <sal/log.hxx>
+#include <string_view>
+
using namespace com::sun::star;
using namespace com::sun::star::uno;
using namespace com::sun::star::lang;
#ifdef MACOSX
constexpr OUString our_sFirebirdLibVar = u"LIBREOFFICE_FIREBIRD_LIB"_ustr;
#endif
+
+// The name of the file in the firebird directory that maps a name to the absolute path of a
+// database. firebird looks a name up here before it treats it as a path, and re-reads the file
+// whenever it has changed.
+constexpr OUString our_sDatabasesConfName = u"databases.conf"_ustr;
+
+// Put rContent in the file at rURL, discarding whatever was there before.
+bool writeFile(const OUString& rURL, std::string_view aContent)
+{
+ ::osl::File aFile(rURL);
+ if (aFile.open(osl_File_OpenFlag_Create | osl_File_OpenFlag_Write) != ::osl::FileBase::E_None)
+ {
+ if (aFile.open(osl_File_OpenFlag_Write) != ::osl::FileBase::E_None)
+ return false;
+ if (aFile.setSize(0) != ::osl::FileBase::E_None)
+ {
+ aFile.close();
+ return false;
+ }
+ }
+
+ bool bWholeContentWritten = true;
+ if (!aContent.empty())
+ {
+ sal_uInt64 nWritten = 0;
+ bWholeContentWritten
+ = aFile.write(aContent.data(), aContent.size(), nWritten) == ::osl::FileBase::E_None
+ && nWritten == aContent.size();
+ }
+ aFile.close();
+ return bWholeContentWritten;
+}
+
+// firebird takes its root directory from the FIREBIRD variable on its first use, and reads the
+// firebird.conf that confines it to that directory only from there. Ask firebird which directory
+// it settled on and treat anything but our own as unconfined.
+bool isFirebirdRootedAt(const OUString& rDataDirPath)
+{
+ Firebird::IMaster* pMaster = Firebird::fb_get_master_interface();
+ if (!pMaster)
+ return false;
+
+ Firebird::IConfigManager* pConfigManager = pMaster->getConfigManager();
+ if (!pConfigManager)
+ return false;
+
+ const char* pRootDirectory = pConfigManager->getRootDirectory();
+ if (!pRootDirectory)
+ return false;
+
+ OUString sRootPath = OStringToOUString(pRootDirectory, osl_getThreadTextEncoding());
+ if (sRootPath.endsWith("/") || sRootPath.endsWith("\\"))
+ sRootPath = sRootPath.copy(0, sRootPath.getLength() - 1);
+
+#if defined(_WIN32)
+ return sRootPath.equalsIgnoreAsciiCase(rDataDirPath);
+#else
+ return sRootPath == rDataDirPath;
+#endif
+}
};
FirebirdDriver::FirebirdDriver(const css::uno::Reference< css::uno::XComponentContext >& _rxContext)
sDataDirPath = sDataDirPath.toAsciiUpperCase();
#endif
+ // ExternalFileAccess is already None by default. Saying so here keeps a database firebird
+ // opens from reading databases.conf back out of the directory it shares with it.
OString sConf = "DatabaseAccess = Restrict "
- + OUStringToOString(sDataDirPath, RTL_TEXTENCODING_UTF8) + "\n";
+ + OUStringToOString(sDataDirPath, RTL_TEXTENCODING_UTF8)
+ + "\nExternalFileAccess = None\n";
OUString sConfURL = m_firebirdDataDirectory.GetURL() + "/firebird.conf";
- ::osl::File aConfFile(sConfURL);
- if (aConfFile.open(osl_File_OpenFlag_Create | osl_File_OpenFlag_Write) == ::osl::FileBase::E_None)
+ if (writeFile(sConfURL, std::string_view(sConf))
+ // An empty databases.conf, so that firebird has the file in hand from its first use and
+ // notices the names added to it later.
+ && writeFile(m_firebirdDataDirectory.GetURL() + "/" + our_sDatabasesConfName,
+ std::string_view()))
{
- sal_uInt64 nWritten = 0;
- if (aConfFile.write(sConf.getStr(), sConf.getLength(), nWritten) == ::osl::FileBase::E_None
- && nWritten == static_cast<sal_uInt64>(sConf.getLength()))
- {
- osl_setEnvironment(our_sFirebirdRootVar.pData, sDataDirPath.pData);
- m_bConfined = true;
- }
- aConfFile.close();
+ osl_setEnvironment(our_sFirebirdRootVar.pData, sDataDirPath.pData);
+ m_bConfined = isFirebirdRootedAt(sDataDirPath);
}
SAL_WARN_IF(!m_bConfined, "connectivity.firebird",
- "could not write " << sConfURL << ", firebird connections will be refused");
+ "firebird is not confined to " << sDataDirPath << ", connections will be refused");
#ifndef SYSTEM_FIREBIRD
// Overrides firebird's hardcoded default of /usr/local/firebird on *nix,
FirebirdDriver::~FirebirdDriver() = default;
+OUString FirebirdDriver::addExternalDatabaseName(const OUString& rDatabasePath)
+{
+ MutexGuard aGuard(m_aMutex);
+
+ sal_uInt8 aUuid[16];
+ rtl_createUuid(aUuid, nullptr, false);
+
+ OUStringBuffer aName(u"database");
+ for (const sal_uInt8 nByte : aUuid)
+ {
+ aName.append(OUString::number(nByte >> 4, 16) + OUString::number(nByte & 0x0F, 16));
+ }
+ const OUString sName = aName.makeStringAndClear();
+
+ m_aExternalDatabaseNames.emplace(sName, rDatabasePath);
+ writeExternalDatabaseNames();
+
+ return sName;
+}
+
+void FirebirdDriver::removeExternalDatabaseName(const OUString& rName)
+{
+ MutexGuard aGuard(m_aMutex);
+
+ if (m_aExternalDatabaseNames.erase(rName) != 0)
+ writeExternalDatabaseNames();
+}
+
+void FirebirdDriver::writeExternalDatabaseNames()
+{
+ MutexGuard aGuard(m_aMutex);
+
+ OStringBuffer aContent;
+ for (const auto& [rName, rPath] : m_aExternalDatabaseNames)
+ {
+ aContent.append(OUStringToOString(rName, RTL_TEXTENCODING_UTF8) + " = "
+ + OUStringToOString(rPath, RTL_TEXTENCODING_UTF8) + "\n");
+ }
+ const OString sContent = aContent.makeStringAndClear();
+
+ const OUString sURL = m_firebirdDataDirectory.GetURL() + "/" + our_sDatabasesConfName;
+ SAL_WARN_IF(!writeFile(sURL, std::string_view(sContent)), "connectivity.firebird",
+ "could not write " << sURL);
+}
+
void FirebirdDriver::disposing()
{
MutexGuard aGuard(m_aMutex);
}
rtl::Reference<Connection> pCon = new Connection();
- pCon->construct(url, info, getDatabaseDataDirectoryURL());
+ pCon->construct(url, info, *this);
m_xConnections.emplace_back(*pCon);
#include <cppuhelper/compbase.hxx>
#include <unotools/tempfile.hxx>
+#include <map>
+
namespace connectivity::firebird
{
// The SQL dialect in use
::utl::TempFileNamed m_firebirdDataDirectory;
bool m_bConfined;
+ // The names firebird may use for a database that lies outside its own directory, each
+ // mapped to that database's absolute path. The names are random, and only the databases
+ // the user picked in this session are in here.
+ std::map<OUString, OUString> m_aExternalDatabaseNames;
+
+ // Write m_aExternalDatabaseNames out as the databases.conf of the firebird directory.
+ void writeExternalDatabaseNames();
+
protected:
::osl::Mutex m_aMutex; // mutex is need to control member access
OWeakRefArray m_xConnections; // vector containing a list
// associated files it creates for them under this directory as well.
OUString getDatabaseDataDirectoryURL() const { return m_firebirdDataDirectory.GetURL(); }
+ // Give firebird a name for a database that lies outside its own directory, and return
+ // that name. firebird resolves a name it finds in databases.conf without applying the
+ // DatabaseAccess restriction, while every path it is handed elsewhere, a difference file
+ // or a shadow, is still checked against that restriction. The name is random so that a
+ // database firebird opens cannot ask for another database by guessing the name.
+ OUString addExternalDatabaseName(const OUString& rDatabasePath);
+
+ // Take back a name from addExternalDatabaseName. Any database already attached through
+ // it stays open.
+ void removeExternalDatabaseName(const OUString& rName);
+
// OComponentHelper
virtual void SAL_CALL disposing() override;